<?php // $Id: aca.class.php,v 1.8.2.9 2006/01/19 15:25:20 skodak Exp $

/**
 * Extend the base AC assignment class for assignments where you upload a Pascal source files
 *
 */

//allowed extension of files (with ".") 
$ACACFG->allowext = array('.pas','.pp','.inc','.dat','.txt');   // allowed types for upload
$ACACFG->includeext = array('.pas','.pp');                      // allowed types for including
$ACACFG->tokenizeext = array('.pas','.pp');                     // allowed types to tokenize

// base language class
class aca_pascal extends aca_base {
   
    //CONSTRUCTOR
    function aca_pascal($cmid=0) {
        parent::aca_base($cmid);
    }
    
    /**
     * Fucntion for make settings
     * @param object $tv - row from aca_tv_active table
     * @param string $type - 'form' or 'head' - content of output array
     * @param int $multi - edit more test vectors in one form, or only one (0)
     * @return
     **/
    function pascal_tv_setting($tvid,$tv,$type='form',$multi=1) {
        $multi = ($multi==1)? "[$tv->id]" : "";
        switch ($tvid) {
            case 4: {
                if ($type=='head') {
                    $ret['testopt'] = get_string('tvpascal4testopt','aca');
                    $ret['inopt'] = get_string('tvpascal4inopt','aca');
                    $ret['intext'] = get_string('tvpascal4in_text','aca');
                    $ret['instring'] = get_string('tvpascal4in_string','aca');
                }
                else {
                    $choose[0]=get_string('tvpascal4testopt0','aca');
                    $choose[1]=get_string('tvpascal4testopt1','aca');
                    $choose[2]=get_string('tvpascal4testopt2','aca');
                    $choose[3]=get_string('tvpascal4testopt3','aca');
                    $choosemenu = choose_from_menu($choose,'test_opt'.$multi,$tv->test_opt,'','','',true);
                    $ret['testopt']=$choosemenu;
                    
                    unset($choose);
                    $choose[0]=get_string('tvpascal4inopt0','aca');
                    $choose[1]=get_string('tvpascal4inopt1','aca');
                    
                    $choosemenu = choose_from_menu($choose,'in_opt'.$multi,$tv->in_opt,'','','',true);
                    $ret['inopt']=$choosemenu;
                    $ret['intext']="<textarea name=\"in_text$multi\" rows=\"3\" cols=\"20\">$tv->in_text</textarea>";
                    $ret['instring']="<input type=\"text\" size=\"30\" name=\"in_string$multi\" value=\"$tv->in_string\">";
                }
            }
            break;
        }
        return $ret;
    }
    
    /**
     * function to process one tv
     * function "know how" to process data (for each ID is different testing method)
     * @param object $tv test vector object from DB (aca_tv_active)
     **/
    function pascal_tv_testing($tv,$submission=0) {
        global $USER,$CFG,$ACACFG,$course;
        
        //check if result is exists
        $test=false;
        if ($submission!=0) $test = get_record('aca_tv_results','activetvid',$tv->id,'acaid',$this->assignment->id,'submissionid',$submission);
        // create new result or get old data
        if (!$test) $result = $this->new_tv_result($tv,$submission);
        else $result = $test;
        
        $allowed = get_records('aca_tvs','langtype','pascal');
        
        //identification
        $teacher = isteacher($course->id, $USER->id);
        $teachertest = ($teacher && $submission==0);
        
        //directories
        $testdir = make_upload_directory($this->file_area_name($USER->id,1).'test',false);
        $sourcedir = $studentdir = $CFG->dataroot.'/';
        
        $sourcedir .= $this->file_area_name($USER->id,1);
        
        if ($teachertest) $studentdir = $sourcedir;
        elseif ($teacher) {
            $uid = get_field('aca_submissions','userid','id',$submission);
            $studentdir .= $this->file_area_name($uid);
        }
        else $studentdir .= $this->file_area_name($USER->id);
        
        // content of directories
        $dir = opendir($sourcedir);
        while (false !== ($file = readdir($dir))) {
            if ($file!='.' && $file!= '..') $sourcefiles[] = $file;
        }
        closedir($dir);
        $dir = opendir($studentdir);
        while (false !== ($file = readdir($dir))) {
            if ($file!='.' && $file!= '..') $studentfiles[] = $file;
        }
        closedir($dir);
                
        // main switch of TVID (ID from aca_tvs)
        switch ($tv->tvid) {
            case 4: {   //content test
                if (!isset($allowed[4])) error("Invalid test processing");
                
                // test prefered file
                $pref_file = '';
                if ($tv->in_string != '') {
                    if (in_array($tv->in_string,$sourcefiles) && in_array($tv->in_string,$studentfiles)) $pref_file = $tv->in_string;
                    else error('Missing prefered file');
                }
                
                /* two steps of testing
                 1) Copy ALL sourcefiles to testdir and catch output
                 2) Copy ALL studentfiles and catch output
                */
                for ($i=1;$i<=2;$i++) {
                    // relocate dir and files (source, student)
                    if ($i==1) {
                        $nowfiles = $sourcefiles;
                        $nowdir = $sourcedir;
                    }
                    else {
                        $nowfiles = $studentfiles;
                        $nowdir = $studentdir;
                    }
                    
                    if ($this->copy_all_files($nowfiles,$nowdir,$testdir)) {
                        
                        // parameters
                        $params = '';
                        if ($tv->in_text != '') {
                            if ($tv->in_opt == 0) $params = $tv->in_text;
                            else $params = '../source/'.$tv->in_text;
                        }
                        
                        // prefered file
                        if ($pref_file == '') {
                            foreach ($nowfiles as $val) {
                                $ext=strtolower(strrchr($val,".")); 
                                if (in_array($ext,$ACACFG->includeext)) {
                                    $test_file = $val;
                                    break;
                                }
                            }
                        }
                        else $test_file = $pref_file;
                        
                        // get name of compiled file
                        $extlen=strlen(strrchr($test_file,"."));
                        $filetoexec = $testdir.'/'.substr($test_file,0,-$extlen);
                        @chmod($filetoexec,0777);    // we must allow exec this file
                        
                        $compiletest = false;
                        if (file_exists($filetoexec)) {
                            if (!is_executable($filetoexec)) $compiletest = $this->compilefile($nowdir,$test_file);
                            else $compiletest = true;
                        }
                        else $compiletest = $this->compilefile($nowdir,$test_file);
                        
                        // if file exist and can be executed, we can test it
                        if ($compiletest) {
                            // if not present, we must copy it from it's dir
                            if (!file_exists($filetoexec)) copy($nowdir.'/'.substr($test_file,0,-$extlen),$filetoexec);
                            
                            // begin of exec for autokill script
                            $outfiles = $ACACFG->tmppath.'/'.uniqid("");
                            $execbegin = "$ACACFG->autokill $ACACFG->killtimeout $outfiles ";
                            
                            $exec = $execbegin."$filetoexec $params";
                            //echo "Command to execute: ".$exec;
                            exec($exec,$output,$exitcode);
                            
                            $resfile[$i]='';
                            if ($exitcode == 0) {   // everything seems OK
                                if (file_exists($outfiles.'.out')) {
                                    $out[$i] = file_get_contents($outfiles.'.out');
                                    $resfile[$i] = $outfiles.'.out';
                                }
                                elseif (file_exists($outfiles.'.err')) {
                                    $out[$i] = get_string('outputerrors','aca')."\n".file_get_contents($outfiles.'.err');
                                    $resfile[$i] = $outfiles.'.err';
                                }
                                // unknown error, because output files not exists
                                else $out[$i] = get_string('unknownerroroutput','aca');
                            }
                            // something wrong, because timeout exceeded
                            else $out[$i] = get_string('timeexceeded','aca');
                        }
                        // if we can't test it, write test error
                        else {
                            $out[$i] = get_string('filenotcompiled','aca');
                        }
                        
                        if (!$this->delete_all_files($testdir)) notify('Can\'t delete files');
                    }
                    else error('Can\'t move files');
                }
                
                $testresult = 0;
                $diffs = '';
                // get differences between files
                if ($compiletest && $resfile[1]!='' && $resfile[2]!='') {
                    // diff options
                    $diffopt = '';
                    switch ($tv->test_opt) {
                        case 0: $diffopt = '';
                        break;
                        case 1: $diffopt = 'bB';
                        break;
                        case 2: $diffopt = 'i';
                        break;
                        case 3: $diffopt = 'ibB';
                        break;
                    }
                    //get differences from diff
                    $exec = 'diff -p'.$diffopt.' '.$resfile[1].' '.$resfile[2];
                    //echo "Command to execute: ".$exec;
                    $diff = `$exec`;
                    // if sizeof diff = 0, outputs are same
                    if ($diff != '') $diffs = $diff;
                    else $testresult = 1;
                    //delete files
                    $resfilebase = substr($resfile[1],0,-3);
                    unlink ($resfilebase.'out');unlink ($resfilebase.'err');
                    $resfilebase = substr($resfile[2],0,-3);
                    unlink ($resfilebase.'out');unlink ($resfilebase.'err');
                }
                //if something wrong, write "diff like code"
                else $diffs = "---\n---\n!".$out[1].$out[2];
                
                //process results
                $result->indata = ($tv->in_opt == 0) ? $tv->in_text : file_get_contents($sourcedir.'/'.$tv->in_text);
                $result->teacher_out = $out[1];
                $result->student_out = $out[2];
                $result->result = $testresult;
                $result->description = $diffs;
            }   // default test end
            break;
        }
        // save to db or return
        if ($submission!=0) {
            if (!$test) insert_record('aca_tv_results',$result);
            else update_record('aca_tv_results',$result);
        }
        else return $result;
    }
    
    // compile for future use
    function compilefile($dir,$test_file) {
        global $ACACFG,$CFG;
        
        $ret = false;
        
        $extlen=strlen(strrchr($test_file,"."));
        $filetoexec = $dir.'/'.substr($test_file,0,-$extlen);
        
        // delete if exists and can't be executed
        if (file_exists($filetoexec)) unlink($filetoexec);
        
        chdir($dir);
        
        $outfiles = $ACACFG->tmppath.'/'.uniqid("");
        $execbegin = "$ACACFG->autokill $ACACFG->killtimeout $outfiles ";
        
        $exec = $execbegin."fpc $test_file";
        //echo "Command to execute: ".$exec;
        exec($exec,$output,$exitcode);
        
        if ($exitcode == 0) {
            $ret = file_exists($filetoexec);
        }
        //delete files
        @unlink($outfiles.'.out');
        @unlink($outfiles.'.err');
        return $ret;
    }
    
    /**
     * Function prits or return information about student's submission
     * @param int $userid ID of user
     * @param boolean $return return or echo the output
     * @return string
     **/
    function print_student_answer($userid, $return=false){
        global $CFG, $USER;
       
        $filearea = $this->file_area_name($userid);

        $output = '';
            
        if ($basedir = $this->file_area($userid)) {
            if ($files = get_directory_list($basedir)) {
                
                foreach ($files as $key => $file) {
                    require_once($CFG->libdir.'/filelib.php');
                    
                    $icon = mimeinfo('icon', $file);
                    
                    if ($CFG->slasharguments) {
                        $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
                    } else {
                        $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
                    }
                    //died right here
                    //require_once($ffurl);                
                    $output .= '<img align="middle" src="'.$CFG->pixpath.'/f/'.$icon.'" height="16" width="16" alt="'.$icon.'" />'.
                            '<a href="'.$ffurl.'" >'.$file.'</a><br />';
                }
            }
        }

        $output = '<div class="files">'.$output.'</div>';
        
        $testresults = link_to_popup_window($CFG->wwwroot.'//mod/aca/testvectors.php?id='.$this->cm->id.'&amp;a='.$this->assignment->id.'&amp;tv=0&amp;uid='.$userid,
                                            'testresults',get_string('testresults','aca'),600,800,get_string('popupwindow'),'none',true);
        $output = '<div>'.$testresults.'</div>'.$output;

        return $output;    
    }
    
    /**
     * function to print button to test one submission
     * (called by submissions function)
     * @param int $userid - user ID
     * @return string to echo
     **/
    function print_tv_test_button($userid) {
        return button_to_popup_window('/mod/aca/testvectors.php?id='.$this->cm->id.'&amp;a='.$this->assignment->id.'&amp;tv=0&amp;uid='.$userid.'&amp;test=1',
                                      'runtest',get_string('runtest','aca'),600,800,get_string('popupwindow'),'none',true);
    }
    
    /**
     * function to print button to test all submissions in assignment
     * (called by submissions function)
     * @return string to echo
     **/
    function print_tv_testall_button() {
        // additional form for testing
        echo '<form name="testing" action="submissions.php?id='.$this->cm->id.'" method="post">';
        echo '<table id="testtable" align="center"><tr>';
        echo '<input type="hidden" name="testall" value="'.$this->assignment->id.'">';
        echo '<td><input type="submit" name="testbutton" value="'.get_string('runalltest','aca').'"></td>';
        echo '</tr></table>';
        echo '</form>';
    }
    
    /**
     * Function to view data about assignment in view.php page
     **/
    function view() {

        global $USER, $course;

        $this->view_header();

        $this->view_intro();

        $this->view_dates();
        
        $teacher = isteacher($course->id, $USER->id);
        
        // pd settings and creating
        if ($teacher) {
            //creating
            $action = optional_param('action','');
            if ($action == 'on') {
                $pdid = optional_param('pdid',0);
                if ($pdid) $this->create_pd($pdid);
            }
            //deleting
            elseif ($action == 'off') {
                $pd = optional_param('pd',0);
                if ($pd) $this->delete_pd($pd);
            }
            //setting
            $pdsetting = optional_param('pdsetting',0);
            if ($pdsetting) {
                $this->process_pds();
            }
        }
        
        $test = optional_param('test',0);
        $sid = optional_param('submission',0);
        // if testing, then test and display results
        if ($test == 1 ) {
            if ($teacher) {
                $data = $this->tv_testing($this->assignment,$sid);
                $this->display_tv_results($this->assignment,$sid,$data);
            }
            else {
                if($sid!=0) {
                    $test_res = $this->tv_testing($this->assignment,$sid);
                    $this->display_tv_results($this->assignment,$sid,NULL,$test_res);
                }
                else notify(get_string('testerror','aca'));
            }
        }
        // if student, then view results if can
        elseif (!$teacher && ($this->assignment->showtest > 0)) {
            if ($sid = get_field("aca_submissions","id","userid",$USER->id,"acaid",$this->assignment->id)) {
                $this->display_tv_results($this->assignment,$sid);
            }
        }
        
        $filecount = $this->count_user_files($USER->id);

        if ($submission = $this->get_submission()) {
            if ($submission->timemarked) {
                $this->view_feedback();
            } else if ($filecount) {
                print_simple_box($this->print_user_files($USER->id, true), 'center');
            }
        }
        
        if ($teacher) print_simple_box($this->print_user_files($USER->id, true,1), 'center');
        
        if ($teacher) {
            $this->print_tvs_summary($this->assignment);
            $this->print_pds_settings($this->assignment);
        }

        if (!isguest($USER->id) && !$teacher && $this->isopen() && (!$filecount || $this->assignment->resubmit || !$submission->timemarked)) {
            $this->view_upload_form();
        }

        $this->view_footer();
    }

    
    function view_upload_form() {
        global $CFG;
        $struploadafile = get_string("uploadafile");
        $strmaxsize = get_string("maxsize", "", display_size($this->assignment->maxbytes));

        echo '<center>';
        echo '<form enctype="multipart/form-data" method="post" '.
             "action=\"$CFG->wwwroot/mod/aca/upload.php\">";
        echo "<p>$struploadafile ($strmaxsize)</p>";
        echo '<input type="hidden" name="id" value="'.$this->cm->id.'" />';
        require_once($CFG->libdir.'/uploadlib.php');
        upload_print_form_fragment(1,array('newfile'),false,null,0,$this->assignment->maxbytes,false);
        echo '<input type="submit" name="save" value="'.get_string('uploadthisfile').'" />';
        echo '</form>';
        echo '</center>';
    }
    
    /* upload source file if needed in add_instance od update_instance */
    function upload_source ($assignment) {
        global $CFG,$USER;
        
        $course = get_record("course", "id", $assignment->course);
        
        $dir = $assignment->course.'/'.$CFG->moddata.'/aca/'.$assignment->id.'/source';
        
        require_once($CFG->dirroot.'/lib/uploadlib.php');
        $um = new upload_manager('sourcefile',true,false,$course,false,$assignment->maxbytes);
        $um->process_file_uploads($dir);
        $newfile = $um->get_new_filename();
        $filecount = 0;
        $this->unzip_check($dir,$newfile,$filecount);

        // if no file store old val
        if ($newfile == '') return $assignment->sourcefile;
        else return $newfile;
    }
    
    function upload() {
        global $CFG, $USER;

        if (isguest($USER->id)) {
            error(get_string('guestnoupload','aca'));
        }

        $this->view_header(get_string('upload'));

        $filecount = $this->count_user_files($USER->id);
        $submission = $this->get_submission($USER->id);
        if ($this->isopen() && (!$filecount || $this->assignment->resubmit || !$submission->timemarked)) {
            if ($submission = $this->get_submission($USER->id)) {
                //TODO: change later to ">= 0", to prevent resubmission when graded 0
                if (($submission->grade > 0) and !$this->assignment->resubmit) {
                    notify(get_string('alreadygraded', 'aca'));
                }
            }

            $dir = $this->file_area_name($USER->id);

            require_once($CFG->dirroot.'/lib/uploadlib.php');
            $um = new upload_manager('newfile',true,false,$course,false,$this->assignment->maxbytes);
            if ($um->process_file_uploads($dir)) {
                $newfile_name = $um->get_new_filename();
                $filecount = 0;
                // unpacking and checking contents of upload files
                $check = $this->unzip_check($dir,$newfile_name,$filecount);
                if ($submission) {
                    $submission->timemodified = time();
                    $submission->numfiles     = $filecount;
                    $submission->comment = addslashes($submission->comment);
                    if (update_record("aca_submissions", $submission)) {
                        add_to_log($this->course->id, 'aca', 'upload', 
                                'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
                        //prepare for PD
                        $this->preprocess_pd_files($this->assignment,$submission);
                        $this->email_teachers($submission);
                        if ($check) print_heading(get_string('uploadedfile'));
                    } else {
                        notify(get_string("uploadfailnoupdate", "aca"));
                    }
                } else {
                    $newsubmission = $this->prepare_new_submission($USER->id);
                    $newsubmission->numfiles = $filecount;
                    if ($newsubmission->id = insert_record('aca_submissions', $newsubmission)) {
                        add_to_log($this->course->id, 'aca', 'upload', 
                                'view.php?a='.$this->assignment->id, $this->assignment->id, $this->cm->id);
                        //prepare for PD
                        $this->preprocess_pd_files($this->assignment,$newsubmission);
                        $this->email_teachers($newsubmission);
                        if ($check) print_heading(get_string('uploadedfile'));
                    } else {
                        notify(get_string("uploadnotregistered", "aca", $newfile_name) );
                    }
                }
            }
        } else {
            notify(get_string("uploaderror", "aca")); //submitting not allowed!
        }
        
        $sid = (isset($submission->id)) ? $submission->id : $newsubmission->id;
        if ($sid == false) $sid = 0;
        echo '<table align="center"><tr><td>';
        print_single_button('view.php?id='.$this->cm->id,array('test'=>'1','submission'=>$sid),get_string('continue'),'post');
        echo '</td></tr></table>';

        $this->view_footer();
    }
    
    /**
     * function to unpack zip file and check content
     * @param path $dir - dir to unzip
     * @param path $newfile - path to uploaded file
     * @param int $filecount - count of "good" unpacked files
     **/
    function unzip_check($dir,$newfile,&$filecount) {
        global $CFG,$ACACFG;
                
        $ret = false;
        $filecount = 0;
        $dir = $CFG->dataroot.'/'.$dir;
        
        // extension test
        $extension = strtolower(strrchr($newfile,"."));
        // files to delete
        if($extension==".zip") {
            //unzip to temp dir
            if (unzip_file($dir.'/'.$newfile,$ACACFG->unzipdir,false)) {
                // look for unzipped files and delete others
                $delfiles = '';
                $handle=opendir($ACACFG->unzipdir);
                while(($tmpfile=readdir($handle))!==false) {
                    if ($tmpfile != "." && $tmpfile != "..") {
                        $ext=strtolower(strrchr($tmpfile,"."));
            			if (!in_array($ext,$ACACFG->allowext)) $delfiles .= "$tmpfile, ";
            			else {
            			    if (@!copy($ACACFG->unzipdir.$tmpfile,$dir.'/'.$tmpfile)) notify(get_string('filenotcopy','aca',$tmpfile));
            			    else {
                                $ret = true;
                                $filecount ++;
                            }
            			}
        			}
        		}
        		// and one file is the archive
        		$filecount++;
        		closedir($handle);
        		if ($delfiles != '') notify(get_string('filenotallowed','aca',substr($delfiles,0,-2)));
        		
                //delete all files in dir
        		$delok = '';
        		$handle=opendir($ACACFG->unzipdir);
        		while(($tmpfile=readdir($handle))!==false) {
        			if ($tmpfile != "." && $tmpfile != "..") {
                        if (@!unlink($ACACFG->unzipdir.$tmpfile)) $delok .="$tmpfile, ";
                    }
        		}
        		closedir($handle);
        		if ($delok != '') notify(get_string('filenotdelete','aca',substr($delok,0,-2)));
            }
            else {
                notify('Can\'t unzip file');
                if (@!unlink($dir.'/'.$newfile)) notify(get_string('filenotdelete','aca',$newfile));
            }
        }
        // else filter file by extension
        elseif (!in_array($extension,$ACACFG->allowext)) {
            notify(get_string('filenotallowed','aca',$newfile));
            if (@!unlink($dir.'/'.$newfile)) notify(get_string('filenotdelete','aca',$newfile));
        }
        else {
            $ret = true;
            $filecount = 1;
        }
        return $ret;
    }

    /*
     *  Display and process the submissions 
     */ 
    function process_feedback() {                 
                
        global $USER;
         
        if (!$feedback = data_submitted()) {      // No incoming data?
            return false;
        }     
                          
        ///For save and next, we need to know the userid to save, and the userid to go...
        ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
        ///as the userid to store...
        if ((int)$feedback->saveuserid !== -1){
            $feedback->userid = $feedback->saveuserid;
        }       
        
        if (!empty($feedback->cancel)) {          // User hit cancel button
            return false;
        }       
        
        $newsubmission = $this->get_submission($feedback->userid, true);  // Get or make one
                
        $newsubmission->grade      = $feedback->grade;
        $newsubmission->comment    = $feedback->comment;
        $newsubmission->format     = $feedback->format;
        $newsubmission->teacher    = $USER->id;
        $newsubmission->mailed     = 0;       // Make sure mail goes out (again, even)
        $newsubmission->timemarked = time();

        if (! update_record('aca_submissions', $newsubmission)) {
            return false;
        }
        
        add_to_log($this->course->id, 'aca', 'update grades', 
                   'submissions.php?id='.$this->assignment->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);   
        
        return $newsubmission;
                 
    }   
}

?>
